package com.ipay.service.impl;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.ipay.dao.SysSequenceDao;
import com.ipay.entity.BizSequenceType;
import com.ipay.entity.SysSequence;
import com.ipay.exception.BaseException;
import com.ipay.service.SequenceManager;
import com.ipay.utils.DateUtils;
import com.ipay.utils.SpringContextUtils;
import com.ipay.utils.StringUtil;

/**
 * 获取流水号
 * @author yangk
 *
 */
@Component("defaultSequenceManager")
public class DefaultSequenceManager implements SequenceManager{	
	
	public static final Map<String, String> mapping = new HashMap<String, String>();
	
	static{
		mapping.put(NAME_COMMON, "C"); 
	}
	
	@Autowired
	private SysSequenceDao dao;
	
	//@Value("${system.type}")
	private String systemType = "pay";
	
	private int step = 1;// 每次请求流水号个数
	
	private long maxVal = 999999999L;
	
	/**
	 * 业务类型编码+yyyyMMddHH+000000001  共20位
	 * 如：W2017092418000000001
	 */
	
	// key : type + "_" + name
	private ConcurrentMap<String, Sequence> currentCache = new ConcurrentHashMap<String, Sequence>();

	static class Sequence {
		public Long currentVal;
		public Long maxVal;
		public Sequence(Long currentVal, Long maxVal) {
			this.currentVal = currentVal;
			this.maxVal = maxVal;
		}
	}
	
	public static DefaultSequenceManager get() {
		return SpringContextUtils.getBean("defaultSequenceManager", DefaultSequenceManager.class);
	}
	
	@Override
	public String next() {
		return this.next(BizSequenceType.COMMON);
	}
	
	/**
	 * @param bizType 需要与mapping中的map绑定 生成流水号前缀，如果没有流水号前缀默认为D
	 */
	@Override
	public synchronized String next(BizSequenceType bizType) {
		String name = bizType.value();
		Sequence current = this.getCurrentCache(systemType, name);
		Long retVal = 1L;
		if(current == null) { //流水号没生成过
			retVal = getByDB(name);
		} else {
			retVal = nextByCache(name);// 取缓存中流水号，返回-1表示需从数据库取
			if(retVal == -1) {
				retVal = this.getByDB(name); // 从数据库取流水号
			}
			
		}
		
		return this.makeSeq(name, retVal);
	}
	
	private Long insertDB(String name){
		SysSequence entity = null;
		Sequence current = this.getCurrentCache(systemType, name);
		if(current != null){ this.clearCurrentCache(systemType, name);}
		entity = new SysSequence();
		entity.setType(systemType);
		entity.setName(name);
		entity.setCurval(step);
		dao.save(entity);
		this.setCurrentCache(systemType, name, new Sequence(1L, entity.getCurval()));
		return 1L;
	}
	
	private Long getByDB(String name) {
		SysSequence currentEntity = dao.getCurrentSequence(systemType, name);
		if(currentEntity == null){ return insertDB(name);}
		Long retSeq = -1L;
		long newSeq = currentEntity.getCurval() + step;
		long currentStep = step;
		if(newSeq > maxVal && currentEntity.getCurval() < maxVal){ 
			newSeq = maxVal;
			currentStep = maxVal - currentEntity.getCurval();
		}
		if(currentEntity.getCurval() == maxVal){ newSeq = step;}
		
		int ret = dao.next(systemType, name, currentEntity.getCurval(), newSeq);
		if(ret <= 0) {
			currentEntity = dao.getCurrentSequence(systemType, name);
			newSeq = currentEntity.getCurval() + step;
			if(newSeq > maxVal && currentEntity.getCurval() < maxVal) {
				newSeq = maxVal;
				currentStep = maxVal - currentEntity.getCurval();
			}
			if(currentEntity.getCurval() == maxVal){ newSeq = step;}
			
			ret = dao.next(systemType, name, currentEntity.getCurval(), newSeq);
			if(ret <= 0) {throw new BaseException("999998", "获取流水号失败！");}
		}
		retSeq = newSeq - currentStep +1;
		this.setCurrentCache(systemType, name, new Sequence(retSeq, newSeq));
		return retSeq;
	}
	
	private Sequence getCurrentCache(String type, String name) {
		String key = type + "_" + name;
		return currentCache.get(key);
	}
	
	private void clearCurrentCache(String type, String name) {
		currentCache.remove(type + "_" + name);
	}
	
	private void setCurrentCache(String type, String name, Sequence seq) {
		currentCache.put(type + "_" + name, seq);
	}
	
	//返回-1表示需要重新生成
	private synchronized Long nextByCache(String name) {
		Sequence current = this.getCurrentCache(systemType, name);
		current.currentVal = current.currentVal + 1;
		if(current.currentVal > current.maxVal){ return -1L;}
		this.setCurrentCache(systemType, name, current);
		return current.currentVal;
	}
	
	
	private String makeSeq(String name, Long value){
		String start = mapping.get(name);
		if(start == null){ start = "D";}
		String date = DateUtils.format(new Date(), "yyyyMMddHH");
		String end = StringUtil.lpadding(value.toString(), 9, "0");
		return start + date + end;
	}
}
